package codebreaker.protocolo;

import java.io.BufferedReader;
import java.io.IOException;
import java.nio.ByteBuffer;

/**
 * Define as operações de um pacote de dados
 * do jogo Code Breaker
 *
 * @author Rodrigo
 */
public abstract class Pacote {       
    
    /**
     * Cria um pacote vazio
     */
    public Pacote() {    
    }  
    
    /**
     * Cria um pacote a partir de um conjunto de bytes
     * @param bytes Conjunto de bytes
     * @param withID Sinaliza se os bytes tem o id do pacote
     */
    public Pacote(byte bytes[], boolean withID) {
	fromBytes(bytes, withID);
    }
    
    /**
     * Muda os atributos do pacote a partir de um
     * conjunto de bytes
     * @param bytes Conjunto de bytes
     * @param withID Sinaliza se os bytes tem o id do pacote
     */
    public void fromBytes(byte bytes[], boolean withID) {
	fromBytes(
		ByteBuffer.wrap(bytes, (withID)? 1 : 0, bytes.length - ((withID)? 1 : 0)));
    }
    
    /**
     * Retorna os atributos do pacote em forma de bytes
     * @param withID Sinaliza se os bytes tem o id do pacote
     * @return Conjunto de bytes
     */
    public byte[] toBytes(boolean withID) {
	ByteBuffer buffer = ByteBuffer.allocate(getTamanho() + ((withID)? 1 : 0));
	
	if (withID)
	    buffer.put(getID());
	
	writeBytes(buffer);
	
	return buffer.array();
    }
    
    /**
     * Muda os atributos do pacote a partir de um buffer de
     * bytes(sem o id do pacote)
     * @param buffer Conjunto de bytes
     */
    protected abstract void fromBytes(ByteBuffer buffer);
    
    /**
     * Retorna o id do pacote.
     * Deve retornar o mesmo valor para
     * qualquer instância.
     * @return Id do pacote
     */
    public abstract byte getID();
    
    /**
     * Retorna tamanho do pacote, sem o id.
     * Deve ser o mesmo valor em todas
     * as instâncias do pacote.
     * @return Tamanho do pacote
     */
    public abstract int getTamanho();
    
    /**
     * Escreve no buffer os bytes dos atributos
     * do pacote, sem o id do pacote.
     * @param buffer Buffer a ser escrito os bytes
     */
    protected abstract void writeBytes(ByteBuffer buffer);
    
    /**
     * Le um vetor de bytes e traduz em um dos
     * pacotes do protocolo.
     * @param in Stream de leitura de bytes
     * @return Algum pacote do protocolo, ou nulo se nao existe
     */    
    public static Pacote ler(java.io.InputStream in) throws IOException {
	byte buffer[] = new byte[256];
	in.read(buffer);
	
	return ler(buffer);
    }
    
    /**
     * Le um vetor de bytes e traduz em um dos
     * pacotes do protocolo.
     * @param buffer Vetor de bytes
     * @return Algum pacote do protocolo, ou nulo se nao existe
     */
    public static Pacote ler(byte buffer[]) {
	
	Pacote pacote = null;
	
	switch (buffer[0]) {
	    case ChatPrivado.ID:
		pacote = new ChatPrivado(buffer, true);
		break;
	    case ChatPublicoToCliente.ID:
		pacote = new ChatPublicoToCliente(buffer, true);
		break;
	    case ChatPublicoToServidor.ID:
		pacote = new ChatPublicoToServidor(buffer, true);
		break;
	    case Chute.ID:
		pacote = new Chute(buffer, true);
		break;
	    case Conexao.ID: 
		pacote = new Conexao(buffer, true);
		break;
	    case Convite.ID:
		pacote = new Convite(buffer, true);
		break;
	    case FimJogo.ID:
		pacote = new FimJogo(buffer, true);
		break;
	    case EstadoJogador.ID:
		pacote = new EstadoJogador(buffer, true);
		break;
	    case PedidoConexao.ID: 
		pacote = new PedidoConexao(buffer, true);
		break;
	    case RespostaConexao.ID: 
		pacote = new RespostaConexao(buffer, true);
		break;
	    case RespostaConvite.ID: 
		pacote = new RespostaConvite(buffer, true);
		break;
	    case -1:
		return null;
	}
	
	return pacote;
    }
}
